home *** CD-ROM | disk | FTP | other *** search
/ Visual Cafe 3 / Visual Cafe 3.ISO / Vcafe / Main.bin / MessageFormat.java < prev    next >
Text File  |  1998-09-22  |  34KB  |  848 lines

  1. /*
  2.  * @(#)MessageFormat.java   1.15 97/01/29
  3.  *
  4.  * (C) Copyright Taligent, Inc. 1996,1997 - All Rights Reserved
  5.  * (C) Copyright IBM Corp. 1996,1997 - All Rights Reserved
  6.  *
  7.  * Portions copyright (c) 1996-1997 Sun Microsystems, Inc. All Rights Reserved.
  8.  *
  9.  *   The original version of this source code and documentation is copyrighted
  10.  * and owned by Taligent, Inc., a wholly-owned subsidiary of IBM. These
  11.  * materials are provided under terms of a License Agreement between Taligent
  12.  * and Sun. This technology is protected by multiple US and International
  13.  * patents. This notice and attribution to Taligent may not be removed.
  14.  *   Taligent is a registered trademark of Taligent, Inc.
  15.  *
  16.  * Permission to use, copy, modify, and distribute this software
  17.  * and its documentation for NON-COMMERCIAL purposes and without
  18.  * fee is hereby granted provided that this copyright notice
  19.  * appears in all copies. Please refer to the file "copyright.html"
  20.  * for further important copyright and licensing information.
  21.  *
  22.  * SUN MAKES NO REPRESENTATIONS OR WARRANTIES ABOUT THE SUITABILITY OF
  23.  * THE SOFTWARE, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
  24.  * TO THE IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
  25.  * PARTICULAR PURPOSE, OR NON-INFRINGEMENT. SUN SHALL NOT BE LIABLE FOR
  26.  * ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF USING, MODIFYING OR
  27.  * DISTRIBUTING THIS SOFTWARE OR ITS DERIVATIVES.
  28.  *
  29.  */
  30.  
  31. package java.text;
  32.  
  33. import java.util.Date;
  34. import java.util.Locale;
  35. import java.text.DecimalFormat;
  36. import java.text.Utility;
  37. /**
  38.  * <code>MessageFormat</code> provides a means to produce concatenated
  39.  * messages in language-neutral way. Use this to construct messages
  40.  * displayed for end users.
  41.  *
  42.  * <p>
  43.  * <code>MessageFormat</code> takes a set of objects, formats them, then
  44.  * inserts the formatted strings into the pattern at the appropriate places.
  45.  *
  46.  * <p>
  47.  * <strong>Note:</strong>
  48.  * <code>MessageFormat</code> differs from the other <code>Format</code>
  49.  * classes in that you create a <code>MessageFormat</code> object with one
  50.  * of its constructors (not with a <code>getInstance</code> style factory
  51.  * method). The factory methods aren't necessary because <code>MessageFormat</code>
  52.  * doesn't require any complex setup for a given locale. In fact,
  53.  * <code>MessageFormat</code> doesn't implement any locale specific behavior
  54.  * at all. It just needs to be set up on a sentence by sentence basis.
  55.  *
  56.  * <p>
  57.  * Here are some examples of usage:
  58.  * <blockquote>
  59.  * <pre>
  60.  * Object[] arguments = {
  61.  *     new Integer(7),
  62.  *     new Date(System.currentTimeMillis()),
  63.  *     "a disturbance in the Force"
  64.  * };
  65.  *
  66.  * String result = MessageFormat.format(
  67.  *     "At {1,time} on {1,date}, there was {2} on planet {0,number,integer}.",
  68.  *     arguments);
  69.  *
  70.  * <output>: At 12:30 PM on Jul 3, 2053, there was a disturbance
  71.  *           in the Force on planet 7.
  72.  *
  73.  * </pre>
  74.  * </blockquote>
  75.  * Typically, the message format will come from resources, and the
  76.  * arguments will be dynamically set at runtime.
  77.  *
  78.  * <p>
  79.  * Example 2:
  80.  * <blockquote>
  81.  * <pre>
  82.  * Object[] testArgs = {new Long(3), "MyDisk"};
  83.  *
  84.  * MessageFormat form = new MessageFormat(
  85.  *     "The disk \"{1}\" contains {0} file(s).");
  86.  *
  87.  * System.out.println(form.format(testArgs));
  88.  *
  89.  * // output, with different testArgs
  90.  * <output>: The disk "MyDisk" contains 0 file(s).
  91.  * <output>: The disk "MyDisk" contains 1 file(s).
  92.  * <output>: The disk "MyDisk" contains 1,273 file(s).
  93.  * </pre>
  94.  * </blockquote>
  95.  *
  96.  * <p>
  97.  * The pattern is of the form:
  98.  * <blockquote>
  99.  * <pre>
  100.  * messageFormatPattern := string ( "{" messageFormatElement "}" string )*
  101.  *
  102.  * messageFormatElement := argument { "," elementFormat }
  103.  *
  104.  * elementFormat := "time" { "," datetimeStyle }
  105.  *                | "date" { "," datetimeStyle }
  106.  *                | "number" { "," numberStyle }
  107.  *                | "choice" { "," choiceStyle }
  108.  *
  109.  * datetimeStyle := "short"
  110.  *                  | "medium"
  111.  *                  | "long"
  112.  *                  | "full"
  113.  *                  | dateFormatPattern
  114.  *
  115.  * numberStyle := "currency"
  116.  *               | "percent"
  117.  *               | "integer"
  118.  *               | numberFormatPattern
  119.  *
  120.  * choiceStyle := choiceFormatPattern
  121.  * </pre>
  122.  * </blockquote>
  123.  * If there is no <code>elementFormat</code>,
  124.  * then the argument must be a string, which is substituted. If there is
  125.  * no <code>dateTimeStyle</code> or <code>numberStyle</code>, then the
  126.  * default format is used (for example, <code>NumberFormat.getInstance</code>,
  127.  * <code>DateFormat.getTimeInstance</code>, or <code>DateFormat.getInstance</code>).
  128.  *
  129.  * <p>
  130.  * In strings, single quotes can be used to quote the "{"
  131.  * (curly brace) if necessary. A real single quote is represented by ''.
  132.  * Inside a <code>messageFormatElement</code>, quotes are <strong>not</strong>
  133.  * removed. For example, {1,number,$'#',##} will produce a number format
  134.  * with the pound-sign quoted, with a result such as: "$#31,45".
  135.  *
  136.  * <p>
  137.  * If a pattern is used, then unquoted braces in the pattern, if any, must match:
  138.  * that is, "ab {0} de" and "ab '}' de" are ok, but "ab {0'}' de" and "ab } de" are
  139.  * not.
  140.  *
  141.  * <p>
  142.  * The argument is a number from 0 to 9, which corresponds to the
  143.  * arguments presented in an array to be formatted.
  144.  *
  145.  * <p>
  146.  * It is ok to have unused arguments in the array.
  147.  * With missing arguments or arguments that are not of the right class for
  148.  * the specified format, a <code>ParseException</code> is thrown.
  149.  * First, <code>format</code> checks to see if a <code>Format</code> object has been
  150.  * specified for the argument with the <code>setFormats</code> method.
  151.  * If so, then <code>format</code> uses that <code>Format</code> object to format the
  152.  * argument. Otherwise, the argument is formatted based on the object's
  153.  * type. If the argument is a <code>Number</code>, then <code>format</code>
  154.  * uses <code>NumberFormat.getInstance</code> to format the argument; if the
  155.  * argument is a <code>Date</code>, then <code>format</code> uses
  156.  * <code>DateFormat.getDateTimeInstance</code> to format the argument.
  157.  * Otherwise, it uses the <code>toString</code> method.
  158.  *
  159.  * <p>
  160.  * For more sophisticated patterns, you can use a <code>ChoiceFormat</code> to get
  161.  * output such as:
  162.  * <blockquote>
  163.  * <pre>
  164.  * MessageFormat form = new MessageFormat("The disk \"{1}\" contains {0}.");
  165.  * double[] filelimits = {0,1,2};
  166.  * String[] filepart = {"no files","one file","{0,number} files"};
  167.  * ChoiceFormat fileform = new ChoiceFormat(filelimits, filepart);
  168.  * form.setFormat(1,fileform); // NOT zero, see below
  169.  *
  170.  * Object[] testArgs = {new Long(12373), "MyDisk"};
  171.  *
  172.  * System.out.println(form.format(testArgs));
  173.  *
  174.  * // output, with different testArgs
  175.  * output: The disk "MyDisk" contains no files.
  176.  * output: The disk "MyDisk" contains one file.
  177.  * output: The disk "MyDisk" contains 1,273 files.
  178.  * </pre>
  179.  * </blockquote>
  180.  * You can either do this programmatically, as in the above example,
  181.  * or by using a pattern (see
  182.  * <a href="java.text.ChoiceFormat.html"><code>ChoiceFormat</code></a>
  183.  * for more information) as in:
  184.  * <blockquote>
  185.  * <pre>
  186.  * form.applyPattern(
  187.  *    "There {0,choice,0#are no files|1#is one file|1#are {0,number,integer} files}.");
  188.  * </pre>
  189.  * </blockquote>
  190.  * <p>
  191.  * <strong>Note:</strong> As we see above, the string produced
  192.  * by a <code>ChoiceFormat</code> in <code>MessageFormat</code> is treated specially;
  193.  * occurances of '{' are used to indicated subformats, and cause recursion.
  194.  * If you create both a <code>MessageFormat</code> and <code>ChoiceFormat</code>
  195.  * programmatically (instead of using the string patterns), then be careful not to
  196.  * produce a format that recurses on itself, which will cause an infinite loop.
  197.  * <p>
  198.  * <strong>Note:</strong> formats are numbered by order of
  199.  * variable in the string.
  200.  * This is <strong>not</strong> the same as the argument numbering!
  201.  * For example: with "abc{2}def{3}ghi{0}...",
  202.  * <ul>
  203.  * <li>format0 affects the first variable {2}
  204.  * <li>format1 affects the second variable {3}
  205.  * <li>format2 affects the second variable {0}
  206.  * <li>and so on.
  207.  * </ul>
  208.  * <p>
  209.  * You can use <code>setLocale</code> followed by <code>applyPattern</code>
  210.  * (and then possibly <code>setFormat</code>) to re-initialize a
  211.  * <code>MessageFormat</code> with a different locale.
  212.  *
  213.  * @see          java.util.Locale
  214.  * @see          Format
  215.  * @see          NumberFormat
  216.  * @see          DecimalFormat
  217.  * @see          ChoiceFormat
  218.  * @version      1.15 29 Jan 1997
  219.  * @author       Mark Davis
  220.  */
  221.  
  222. public class MessageFormat extends Format {
  223.     /**
  224.      * Constructs with the specified pattern.
  225.      * @see MessageFormat#applyPattern
  226.      */
  227.     public MessageFormat(String pattern) {
  228.         applyPattern(pattern);
  229.     }
  230.  
  231.     /**
  232.      * Constructs with the specified pattern and formats for the
  233.      * arguments in that pattern.
  234.      * @see MessageFormat#setPattern
  235.      */
  236.     public void setLocale(Locale theLocale) {
  237.         locale = theLocale;
  238.     }
  239.  
  240.     /**
  241.      * Gets the locale. This locale is used for fetching default number or date
  242.      * format information.
  243.      */
  244.     public Locale getLocale() {
  245.         return locale;
  246.     }
  247.  
  248.  
  249.     /**
  250.      * Sets the pattern. See the class description.
  251.      */
  252.  
  253.     public void applyPattern(String newPattern) {
  254.             StringBuffer[] segments = new StringBuffer[4];
  255.             for (int i = 0; i < segments.length; ++i) {
  256.                 segments[i] = new StringBuffer();
  257.             }
  258.             int part = 0;
  259.             int formatNumber = 0;
  260.             boolean inQuote = false;
  261.             int braceStack = 0;
  262.             maxOffset = -1;
  263.             for (int i = 0; i < newPattern.length(); ++i) {
  264.                 char ch = newPattern.charAt(i);
  265.                 if (part == 0) {
  266.                     if (ch == '\'') {
  267.                         if (i + 1 < newPattern.length()
  268.                             && newPattern.charAt(i+1) == '\'') {
  269.                             segments[part].append(ch);  // handle doubles
  270.                             ++i;
  271.                         } else {
  272.                             inQuote = !inQuote;
  273.                         }
  274.                     } else if (ch == '{' && !inQuote) {
  275.                         part = 1;
  276.                     } else {
  277.                         segments[part].append(ch);
  278.                     }
  279.                 } else  if (inQuote) {              // just copy quotes in parts
  280.                     segments[part].append(ch);
  281.                     if (ch == '\'') {
  282.                         inQuote = false;
  283.                     }
  284.                 } else {
  285.                     switch (ch) {
  286.                     case ',':
  287.                         if (part < 3)
  288.                             part += 1;
  289.                         else
  290.                             segments[part].append(ch);
  291.                         break;
  292.                     case '{':
  293.                         ++braceStack;
  294.                         segments[part].append(ch);
  295.                         break;
  296.                     case '}':
  297.                         if (braceStack == 0) {
  298.                             part = 0;
  299.                             makeFormat(i, formatNumber, segments);
  300.                             formatNumber++;
  301.                         } else {
  302.                             --braceStack;
  303.                             segments[part].append(ch);
  304.                         }
  305.                         break;
  306.                     case '\'':
  307.                         inQuote = true;
  308.                         // fall through, so we keep quotes in other parts
  309.                     default:
  310.                         segments[part].append(ch);
  311.                         break;
  312.                     }
  313.                 }
  314.             }
  315.             pattern = segments[0].toString();
  316.     }
  317.  
  318.  
  319.     /**
  320.      * Gets the pattern. See the class description.
  321.      */
  322.  
  323.     public String toPattern() {
  324.         // later, make this more extensible
  325.         int lastOffset = 0;
  326.         StringBuffer result = new StringBuffer();
  327.         for (int i = 0; i <= maxOffset; ++i) {
  328.             copyAndFixQuotes(pattern, lastOffset, offsets[i],result);
  329.             lastOffset = offsets[i];
  330.             result.append('{');
  331.             result.append(argumentNumbers[i]);
  332.             if (formats[i] == null) {
  333.                 // do nothing, string format
  334.             } else if (formats[i] instanceof DecimalFormat) {
  335.                 if (formats[i].equals(NumberFormat.getInstance(locale))) {
  336.                     result.append(",number");
  337.                 } else if (formats[i].equals(
  338.                                              NumberFormat.getCurrencyInstance(locale))) {
  339.                     result.append(",number,currency");
  340.                 } else if (formats[i].equals(
  341.                                              NumberFormat.getPercentInstance(locale))) {
  342.                     result.append(",number,percent");
  343.                 } else if (formats[i].equals(getIntegerFormat(locale))) {
  344.                     result.append(",number,integer");
  345.                 } else {
  346.                     result.append(",number," +
  347.                                   ((DecimalFormat)formats[i]).toPattern());
  348.                 }
  349.             } else if (formats[i] instanceof SimpleDateFormat) {
  350.                 if (formats[i].equals(DateFormat.getDateInstance(
  351.                                                                DateFormat.DEFAULT,locale))) {
  352.                     result.append(",date");
  353.                 } else if (formats[i].equals(DateFormat.getDateInstance(
  354.                                                                       DateFormat.SHORT,locale))) {
  355.                     result.append(",date,short");
  356.                 } else if (formats[i].equals(DateFormat.getDateInstance(
  357.                                                                       DateFormat.DEFAULT,locale))) {
  358.                     result.append(",date,medium");
  359.                 } else if (formats[i].equals(DateFormat.getDateInstance(
  360.                                                                       DateFormat.LONG,locale))) {
  361.                     result.append(",date,long");
  362.                 } else if (formats[i].equals(DateFormat.getDateInstance(
  363.                                                                       DateFormat.FULL,locale))) {
  364.                     result.append(",date,full");
  365.                 } else if (formats[i].equals(DateFormat.getTimeInstance(
  366.                                                                       DateFormat.DEFAULT,locale))) {
  367.                     result.append(",time");
  368.                 } else if (formats[i].equals(DateFormat.getTimeInstance(
  369.                                                                       DateFormat.SHORT,locale))) {
  370.                     result.append(",time,short");
  371.                 } else if (formats[i].equals(DateFormat.getTimeInstance(
  372.                                                                       DateFormat.DEFAULT,locale))) {
  373.                     result.append(",time,medium");
  374.                 } else if (formats[i].equals(DateFormat.getTimeInstance(
  375.                                                                       DateFormat.LONG,locale))) {
  376.                     result.append(",time,long");
  377.                 } else if (formats[i].equals(DateFormat.getTimeInstance(
  378.                                                                       DateFormat.FULL,locale))) {
  379.                     result.append(",time,full");
  380.                 } else {
  381.                     result.append(",date,"
  382.                                   + ((SimpleDateFormat)formats[i]).toPattern());
  383.                 }
  384.             } else if (formats[i] instanceof ChoiceFormat) {
  385.                 result.append(",choice,"
  386.                               + ((ChoiceFormat)formats[i]).toPattern());
  387.             } else {
  388.                 //result.append(", unknown");
  389.             }
  390.             result.append('}');
  391.         }
  392.         copyAndFixQuotes(pattern, lastOffset, pattern.length(), result);
  393.         return result.toString();
  394.     }
  395.  
  396.     /**
  397.      * Sets formats to use on parameters.
  398.      * See the class description about format numbering.
  399.      */
  400.     public void setFormats(Format[] newFormats) {
  401.         try {
  402.             formats = (Format[]) newFormats.clone();
  403.         } catch (Exception e) {
  404.             return; // should never occur!
  405.         }
  406.     }
  407.  
  408.     /**
  409.      * Sets formats individually to use on parameters.
  410.      * See the class description about format numbering.
  411.      */
  412.     public void setFormat(int variable, Format newFormat) {
  413.         formats[variable] = newFormat;
  414.     }
  415.  
  416.     /**
  417.      * Gets formats that were set with setFormats.
  418.      * See the class description about format numbering.
  419.      */
  420.     public Format[] getFormats() {
  421.         try {
  422.             return (Format[]) formats.clone();
  423.         } catch (Exception e) {
  424.             return formats; // should never occur!
  425.         }
  426.     }
  427.  
  428.     /**
  429.      * Returns pattern with formatted objects.
  430.      * @param source an array of objects to be formatted & substituted.
  431.      * @param result where text is appended.
  432.      * @param ignore no useful status is returned.
  433.      */
  434.     public final StringBuffer format(Object[] source, StringBuffer result,
  435.                                      FieldPosition ignore)
  436.     {
  437.         return format(source,result,ignore, 0);
  438.     }
  439.  
  440.     /**
  441.      * Convenience routine.
  442.      * Avoids explicit creation of MessageFormat,
  443.      * but doesn't allow future optimizations.
  444.      */
  445.     public static String format(String pattern, Object[] arguments) {
  446.             MessageFormat temp = new MessageFormat(pattern);
  447.             return temp.format(arguments);
  448.     }
  449.  
  450.     // Overrides
  451.     public final StringBuffer format(Object source, StringBuffer result,
  452.                                      FieldPosition ignore)
  453.     {
  454.         return format((Object[])source,result,ignore, 0);
  455.     }
  456.  
  457.     /**
  458.      * Parses the string.
  459.      *
  460.      * <p>Caveats: The parse may fail in a number of circumstances.
  461.      * For example:
  462.      * <ul>
  463.      * <li>If one of the arguments does not occur in the pattern.
  464.      * <li>If the format of an argument is loses information, such as
  465.      *     with a choice format where a large number formats to "many".
  466.      * <li>Does not yet handle recursion (where
  467.      *     the substituted strings contain {n} references.)
  468.      * <li>Will not always find a match (or the correct match)
  469.      *     if some part of the parse is ambiguous.
  470.      *     For example, if the pattern "{1},{2}" is used with the
  471.      *     string arguments {"a,b", "c"}, it will format as "a,b,c".
  472.      *     When the result is parsed, it will return {"a", "b,c"}.
  473.      * <li>If a single argument is formatted twice in the string,
  474.      *     then the later parse wins.
  475.      * </ul>
  476.      */
  477.     public Object[] parse(String source, ParsePosition status) {
  478.         Object[] resultArray = new Object[10];
  479.         int patternOffset = 0;
  480.         int sourceOffset = status.index;
  481.         ParsePosition tempStatus = new ParsePosition(0);
  482.         for (int i = 0; i <= maxOffset; ++i) {
  483.             // match up to format
  484.             int len = offsets[i] - patternOffset;
  485.             if (len == 0 || pattern.regionMatches(patternOffset,
  486.                                                   source, sourceOffset, len)) {
  487.                 sourceOffset += len;
  488.                 patternOffset += len;
  489.             } else {
  490.                 return null; // leave index as is to signal error
  491.             }
  492.  
  493.             // now use format
  494.             if (formats[i] == null) {   // string format
  495.                 // if at end, use longest possible match
  496.                 // otherwise uses first match to intervening string
  497.                 // does NOT recursively try all possibilities
  498.                 int tempLength = (i != maxOffset) ? offsets[i+1] : pattern.length();
  499.  
  500.                 int next;
  501.                 if (patternOffset >= tempLength) {
  502.                     next = source.length();
  503.                 }else{
  504.                     next = source.indexOf( pattern.substring(patternOffset,tempLength), sourceOffset);
  505.                 }
  506.  
  507.                 if (next < 0) {
  508.                     return null; // leave index as is to signal error
  509.                 } else {
  510.                     resultArray[argumentNumbers[i]]
  511.                         = source.substring(sourceOffset,next);
  512.                     sourceOffset = next;
  513.                 }
  514.             } else {
  515.                 tempStatus.index = sourceOffset;
  516.                 resultArray[argumentNumbers[i]]
  517.                     = formats[i].parseObject(source,tempStatus);
  518.                 if (tempStatus.index == sourceOffset) {
  519.                     return null; // leave index as is to signal error
  520.                 }
  521.                 sourceOffset = tempStatus.index; // update
  522.             }
  523.         }
  524.         int len = pattern.length() - patternOffset;
  525.         if (len == 0 || pattern.regionMatches(patternOffset,
  526.                                               source, sourceOffset, len)) {
  527.             status.index = sourceOffset + len;
  528.         } else {
  529.             return null; // leave index as is to signal error
  530.         }
  531.         return resultArray;
  532.     }
  533.  
  534.     /**
  535.      * Parses the string. Does not yet handle recursion (where
  536.      * the substituted strings contain {n} references.)
  537.      * @exception ParseException if the string can't be parsed.
  538.      */
  539.     public Object[] parse(String source) throws ParseException {
  540.         ParsePosition status  = new ParsePosition(0);
  541.         Object[] result = parse(source, status);
  542.         if (status.index == 0)  // unchanged, returned object is null
  543.             throw new ParseException("MessageFormat parse error!", 0);
  544.  
  545.         return result;
  546.     }
  547.  
  548.     /**
  549.      * Parses the string. Does not yet handle recursion (where
  550.      * the substituted strings contain %n references.)
  551.      */
  552.     public Object parseObject (String text, ParsePosition status) {
  553.         return parse(text, status);
  554.     }
  555.  
  556.     /**
  557.      * Overrides Cloneable
  558.      */
  559.     public Object clone()
  560.     {
  561.         MessageFormat other = (MessageFormat) super.clone();
  562.  
  563.         // clone arrays. Can't do with utility because of bug in Cloneable
  564.         other.formats = (Format[]) formats.clone(); // shallow clone
  565.         for (int i = 0; i < formats.length; ++i) {
  566.             if (formats[i] != null)
  567.                 other.formats[i] = (Format)formats[i].clone();
  568.         }
  569.         // for primitives or immutables, shallow clone is enough
  570.         other.offsets = (int[]) offsets.clone();
  571.         other.argumentNumbers = (int[]) argumentNumbers.clone();
  572.  
  573.         return other;
  574.     }
  575.  
  576.     /**
  577.      * Equality comparision between two message format objects
  578.      */
  579.     public boolean equals(Object obj) {
  580.         if (this == obj)                      // quick check
  581.             return true;
  582.         if (getClass() != obj.getClass())
  583.             return false;
  584.         MessageFormat other = (MessageFormat) obj;
  585.         return (maxOffset == other.maxOffset
  586.                 && pattern.equals(other.pattern)
  587.             && Utility.objectEquals(locale, other.locale)   // does null check
  588.                 && Utility.arrayEquals(offsets,other.offsets)
  589.             && Utility.arrayEquals(argumentNumbers,other.argumentNumbers)
  590.             && Utility.arrayEquals(formats,other.formats));
  591.     }
  592.  
  593.     /**
  594.      * Generates a hash code for the message format object.
  595.      */
  596.     public int hashCode() {
  597.         return pattern.hashCode(); // enough for reasonable distribution
  598.     }
  599.  
  600.  
  601.     // ===========================privates============================
  602.  
  603.     // Mark : Is this the right fix?  (HS)
  604.     private Locale locale = Locale.getDefault();
  605.     private String pattern = "";
  606.     // later, allow more than ten items
  607.     private Format[] formats = new Format[10];
  608.     private int[] offsets = new int[10];
  609.     private int[] argumentNumbers = new int[10];
  610.     private int maxOffset = -1;
  611.  
  612.     /**
  613.      * Constructs with the specified pattern.
  614.      * @see MessageFormat#applyPattern
  615.      */
  616.     private MessageFormat(String pattern, Locale loc) {
  617.         locale = (Locale)loc.clone();
  618.         applyPattern(pattern);
  619.     }
  620.  
  621.     /**
  622.      * Internal routine used by format.
  623.      * @param recursionProtection Initially zero. Bits 0..9 are used to indicate
  624.      * that a parameter has already been seen, to avoid recursion.  Currently
  625.      * unused.
  626.      */
  627.  
  628.     private StringBuffer format(Object[] arguments, StringBuffer result,
  629.                                 FieldPosition status, int recursionProtection) {
  630.         // note: this implementation assumes a fast substring & index.
  631.         // if this is not true, would be better to append chars one by one.
  632.         int lastOffset = 0;
  633.         for (int i = 0; i <= maxOffset; ++i) {
  634.             result.append(pattern.substring(lastOffset, offsets[i]));
  635.             lastOffset = offsets[i];
  636.             int argumentNumber = argumentNumbers[i];
  637.             if (argumentNumber >= arguments.length)
  638.                 throw new IllegalArgumentException("Argument # > Arg length");
  639.             // int argRecursion = ((recursionProtection >> (argumentNumber*2)) & 0x3);
  640.             if (false) { // if (argRecursion == 3){
  641.                 // prevent loop!!!
  642.                 result.append('\uFFFD');
  643.             } else {
  644.                 Object obj = arguments[argumentNumber];
  645.                 String arg;
  646.                 boolean tryRecursion = false;
  647.                 if (formats[i] != null) {
  648.                     arg = formats[i].format(obj);
  649.                     tryRecursion = formats[i] instanceof ChoiceFormat;
  650.                 } else if (obj instanceof Number) {
  651.                     // format number if can
  652.                     arg = NumberFormat.getInstance(locale).format(obj); // fix
  653.                 } else if (obj instanceof Date) {
  654.                     // format a Date if can
  655.                     arg = DateFormat.getDateTimeInstance(DateFormat.SHORT,
  656.                                                        DateFormat.SHORT,
  657.                                                        locale).format(obj);//fix
  658.                 } else if (obj instanceof String) {
  659.                     arg = (String) obj;
  660.  
  661.                 } else {
  662.                     arg = obj.toString();
  663.                     if (arg == null) arg = "null";
  664.                 }
  665.  
  666.                 // recurse if necessary
  667.                 if (tryRecursion && arg.indexOf('{') >= 0) {
  668.                     MessageFormat temp = new MessageFormat(arg, locale);
  669.                     temp.format(arguments,result,status,recursionProtection);
  670.                 } else {
  671.                     result.append(arg);
  672.                 }
  673.             }
  674.         }
  675.         result.append(pattern.substring(lastOffset, pattern.length()));
  676.         return result;
  677.     }
  678.     private static final String[] typeList =
  679.     {"", "", "number", "", "date", "", "time", "", "choice"};
  680.     private static final String[] modifierList =
  681.     {"", "", "currency", "", "percent", "", "integer"};
  682.     private static final String[] dateModifierList =
  683.     {"", "", "short", "", "medium", "", "long", "", "full"};
  684.  
  685.     private void makeFormat(int position, int offsetNumber,
  686.                             StringBuffer[] segments)
  687.     {
  688.  
  689.         // get the number
  690.         int argumentNumber;
  691.         try {
  692.             argumentNumber = Integer.parseInt(segments[1].toString()); // always unlocalized!
  693.             if (argumentNumber < 0 || argumentNumber > 9) {
  694.                 throw new NumberFormatException();
  695.             }
  696.             maxOffset = offsetNumber;
  697.             offsets[offsetNumber] = segments[0].length();
  698.             argumentNumbers[offsetNumber] = argumentNumber;
  699.         } catch (Exception e) {
  700.             throw new IllegalArgumentException("argument number too large at ");
  701.         }
  702.  
  703.         // now get the format
  704.         Format newFormat = null;
  705.         switch (findKeyword(segments[2].toString(), typeList)) {
  706.         case 0:
  707.             // string format
  708.             /*if (!segments[3].equals(""))
  709.               throw new IllegalArgumentException("can't modify string format, at ");
  710.               //*/
  711.         break;
  712.         case 1: case 2:// number
  713.             switch (findKeyword(segments[3].toString(), modifierList)) {
  714.             case 0: // default;
  715.                 newFormat = NumberFormat.getInstance(locale);
  716.                 break;
  717.             case 1: case 2:// currency
  718.                 newFormat = NumberFormat.getCurrencyInstance(locale);
  719.                 break;
  720.             case 3: case 4:// percent
  721.                 newFormat = NumberFormat.getPercentInstance(locale);
  722.                 break;
  723.             case 5: case 6:// integer
  724.                 newFormat = getIntegerFormat(locale);
  725.                 break;
  726.             default: // pattern
  727.                 newFormat = NumberFormat.getInstance(locale);
  728.                 try {
  729.                     ((DecimalFormat)newFormat).applyPattern(segments[3].toString());
  730.                 } catch (Exception e) {
  731.                     throw new IllegalArgumentException(
  732.                                              "Pattern incorrect or locale does not support formats, error at ");
  733.                 }
  734.                 break;
  735.             }
  736.             break;
  737.         case 3: case 4: // date
  738.             switch (findKeyword(segments[3].toString(), dateModifierList)) {
  739.             case 0: // default
  740.                 newFormat = DateFormat.getDateInstance(DateFormat.DEFAULT, locale);
  741.                 break;
  742.             case 1: case 2: // short
  743.                 newFormat = DateFormat.getDateInstance(DateFormat.SHORT, locale);
  744.                 break;
  745.             case 3: case 4: // medium
  746.                 newFormat = DateFormat.getDateInstance(DateFormat.DEFAULT, locale);
  747.                 break;
  748.             case 5: case 6: // long
  749.                 newFormat = DateFormat.getDateInstance(DateFormat.LONG, locale);
  750.                 break;
  751.             case 7: case 8: // full
  752.                 newFormat = DateFormat.getDateInstance(DateFormat.FULL, locale);
  753.                 break;
  754.             default:
  755.                 newFormat = DateFormat.getDateInstance(DateFormat.DEFAULT, locale);
  756.                 try {
  757.                     ((SimpleDateFormat)newFormat).applyPattern(segments[3].toString());
  758.                 } catch (Exception e) {
  759.                     throw new IllegalArgumentException(
  760.                                              "Pattern incorrect or locale does not support formats, error at ");
  761.                 }
  762.                 break;
  763.             }
  764.             break;
  765.         case 5: case 6:// time
  766.             switch (findKeyword(segments[3].toString(), dateModifierList)) {
  767.             case 0: // default
  768.                 newFormat = DateFormat.getTimeInstance(DateFormat.DEFAULT, locale);
  769.                 break;
  770.             case 1: case 2: // short
  771.                 newFormat = DateFormat.getTimeInstance(DateFormat.SHORT, locale);
  772.                 break;
  773.             case 3: case 4: // medium
  774.                 newFormat = DateFormat.getTimeInstance(DateFormat.DEFAULT, locale);
  775.                 break;
  776.             case 5: case 6: // long
  777.                 newFormat = DateFormat.getTimeInstance(DateFormat.LONG, locale);
  778.                 break;
  779.             case 7: case 8: // full
  780.                 newFormat = DateFormat.getTimeInstance(DateFormat.FULL, locale);
  781.                 break;
  782.             default:
  783.                 newFormat = DateFormat.getTimeInstance(DateFormat.DEFAULT, locale);
  784.                 try {
  785.                     ((SimpleDateFormat)newFormat).applyPattern(segments[3].toString());
  786.                 } catch (Exception e) {
  787.                     throw new IllegalArgumentException(
  788.                                              "Pattern incorrect or locale does not support formats, error at ");
  789.                 }
  790.                 break;
  791.             }
  792.             break;
  793.         case 7: case 8:// choice
  794.             try {
  795.                 newFormat = new ChoiceFormat(segments[3].toString());
  796.             } catch (Exception e) {
  797.                 throw new IllegalArgumentException(
  798.                                          "Choice Pattern incorrect, error at ");
  799.             }
  800.             break;
  801.         default:
  802.             throw new IllegalArgumentException("unknown format type at ");
  803.         }
  804.         formats[offsetNumber] = newFormat;
  805.         segments[1].setLength(0);   // throw away other segments
  806.         segments[2].setLength(0);
  807.         segments[3].setLength(0);
  808.     }
  809.  
  810.     private static final int findKeyword(String s, String[] list) {
  811.         s = s.trim().toLowerCase();
  812.         for (int i = 0; i < list.length; ++i) {
  813.             if (s.equals(list[i]))
  814.                 return i;
  815.         }
  816.         return -1;
  817.     }
  818.  
  819.     /**
  820.      * Convenience method that ought to be in NumberFormat
  821.      */
  822.     NumberFormat getIntegerFormat(Locale locale) {
  823.         NumberFormat temp = NumberFormat.getInstance(locale);
  824.         if (temp instanceof DecimalFormat) {
  825.             DecimalFormat temp2 = (DecimalFormat) temp;
  826.             temp2.setMaximumFractionDigits(0);
  827.             temp2.setDecimalSeparatorAlwaysShown(false);
  828.             temp2.setParseIntegerOnly(true);
  829.         }
  830.         return temp;
  831.     }
  832.  
  833.     private static final void copyAndFixQuotes(
  834.                                                String source, int start, int end, StringBuffer target) {
  835.         for (int i = start; i < end; ++i) {
  836.             char ch = source.charAt(i);
  837.             if (ch == '{') {
  838.                 target.append("'{'");
  839.             } else if (ch == '\'') {
  840.                 target.append("''");
  841.             } else {
  842.                 target.append(ch);
  843.             }
  844.         }
  845.     }
  846.  
  847. }
  848.